package pTime;


import javax.swing.*;
import javax.swing.table.*;
import java.util.*;
import java.io.*;
import java.awt.event.*;
import java.math.*;
import java.text.*;


/**
 * @author JC PIERSON
 *
 */
public class TaskTableModel extends AbstractTableModel implements ActionListener {

	private ArrayList<Task> tasks = new ArrayList<Task>();
	private javax.swing.Timer timer = new javax.swing.Timer(300000, this);

	private MessageFormat timeFormat = new MessageFormat("{0,number}:{1,number,00}");
	private DecimalFormat df = new DecimalFormat("0.00");

	private TaskFrame taskFrame = null;

	/**
	 * Creates a new instance of TaskTableModel
	 * @param tf task frame instance
	 */
	public TaskTableModel(TaskFrame tf) {
		taskFrame = tf;
		loadFromFile();
		timer.start();
	}

	/**
	 * Returns the value at the given coordinates.
	 * @param rowIndex row index
	 * @param columnIndex column index
	 * @return appropriate cell value; if the arguments are invalid
	 * it returns <CODE>null</CODE>
	 */
	public Object getValueAt(int rowIndex, int columnIndex) {
		switch (columnIndex) {
		case 0:
			return tasks.get(rowIndex).getName();
		case 1:
			long mins = tasks.get(rowIndex).getConsumption() / 60000;
			BigDecimal hm[] = new BigDecimal((int) mins).divideAndRemainder(new BigDecimal(60));
			return timeFormat.format(hm);
		case 2:
			double tot = 0;
			for (int i=0; i<tasks.size(); i++) {
				Task t = tasks.get(i);
				tot += t.getConsumption();
			}
			double tp = 0;
			if (tot != 0){
				tp = (Double)(tasks.get(rowIndex).getConsumption()/tot) ;
				return df.format(tp);
			}else{
				return "";
			}

		default: return null;
		}
	}

	/**
	 * Returns the row count.
	 * @return row count
	 */
	public int getRowCount() {
		return tasks.size();
	}

	/**
	 * Returns the column count (currently 2).
	 * @return column count
	 */
	public int getColumnCount() {
		return 3;
	}

	/**
	 * Sets a new value of the given cell. If at least one of the
	 * coordinates is invalid it does nothing.
	 * @param aValue new value
	 * @param rowIndex row index
	 * @param columnIndex column index
	 */
	@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		switch (columnIndex) {
		case 0: tasks.get(rowIndex).setName((String) aValue);
		break;
		}
	}

	/**
	 * Returns the name of the given column.
	 * @param column column index
	 * @return column name; if the index is invalid it returns an empty
	 * string
	 */
	@Override
	public String getColumnName(int column) {
		switch (column) {
		case 0: return "Task name";
		case 1: return "Time [h:min]";
		case 2: return "%";
		default: return "";
		}
	}

	/**
	 * Returns the class of the given column.
	 * @param columnIndex column index
	 * @return appropriate class object; if the column index is invalid
	 * it returns <CODE>Void.class</CODE>.
	 */
	@Override
	public Class<?> getColumnClass(int columnIndex) {
		switch (columnIndex) {
		case 0: return String.class;
		case 1: return StringBuffer.class;
		case 2: return StringBuffer.class;
		default: return Void.class;
		}
	}

	/**
	 * Checks whether the given cell is editable.
	 * @param rowIndex row index
	 * @param columnIndex column index
	 * @return <CODE>true</CODE> for the first column (index 0),
	 * <CODE>false</CODE> otherwise
	 */
	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return columnIndex == 0;
	}

	public Task getTask(int index) {
		return tasks.get(index);
	}

	/**
	 * Creates a new task.
	 */
	public void addNewTask() {
		Task t = new Task();
		t.setActionListener(this);
		tasks.add(t);

		int row = tasks.size()-1;
		fireTableRowsInserted(row, row);
	}

	/**
	 * Removes the given tasks.
	 * @param start start index
	 * @param end end index (including)
	 */
	public void removeTasks(int start, int end) {
		for (int i=end; i>=start; i--) {
			Task t = tasks.remove(i);
			t.stop();
			t.setActionListener(null);
		}

		fireTableRowsDeleted(start, end);
	}

	/**
	 * Starts the given tasks.
	 * @param start start index
	 * @param end end index (including)
	 */
	public void startTasks(int start, int end) {
		for (int i=start; i<=end; i++) {
			Task t = tasks.get(i);
			t.start();
			fireTableCellUpdated(i, 1);
			fireTableCellUpdated(i, 2);
		}        
	}

	/**
	 * Stops the given tasks.
	 * @param start start index
	 * @param end end index (including)
	 */
	public void stopTasks(int start, int end) {
		for (int i=start; i<=end; i++) {
			Task t = tasks.get(i);
			t.stop();
			fireTableCellUpdated(i, 1);
			fireTableCellUpdated(i, 2);
		}
	}

	/**
	 * Stops all tasks.
	 */
	public void stopAllTasks() {
		Iterator<Task> it = tasks.iterator();
		while (it.hasNext()) {
			it.next().stop();
		}

		fireTableDataChanged();
	}

	/**
	 * Resets the given tasks.
	 * @param start index of the first resetted task
	 * @param end index of the first NOT resetted task (the first task beyond the interval)
	 */
	public void resetTasks(int start, int end) {
		for (int i=start; i<=end; i++) {
			Task t = tasks.get(i);
			t.setConsumption(0);
			fireTableCellUpdated(i, 1);
			fireTableCellUpdated(i, 2);
		}
	}

	/**
	 * Destroys the timer controlling automatic data saving.
	 */
	public void cancelAutoSave() {
		timer.stop();
	}

	/**
	 * Returns the absolute path to the directory where LWTT data should be
	 * saved.
	 * @return directory path
	 */
	public static File getDir() {
		Properties sys = System.getProperties();
		return new File(sys.getProperty("user.dir"));
	}

	/**
	 * Returns the absolute path to the file where LWTT data should be
	 * saved.
	 * @return absolute file path
	 */
	public static File getPath() {
		return new File(getDir(), "data.xml");
	}

	/**
	 * Checks whether the given task is running.
	 * @param index task index
	 * @return <CODE>true</CODE> for running task,
	 * <CODE>false</CODE> otherwise
	 */
	public boolean isRunning(int index) {
		return tasks.get(index).isRunning();
	}

	/**
	 * Loads application's data from the file.
	 */
	public synchronized void loadFromFile() {
		tasks.clear();

		File file = getPath();
		if (!file.exists()) {
			Properties props = new Properties();
			taskFrame.setStartSettings(props);
			return;
		}

		try {
			FileInputStream is = new FileInputStream(file);
			Properties props = new Properties();
			props.loadFromXML(is);
			is.close();

			taskFrame.setStartSettings(props);

			Iterator<Object> it = props.keySet().iterator();
			while (it.hasNext()) {
				String key = (String) it.next();
				if (key.endsWith(".name")) {
					String ids = key.substring(0, key.length() - 5);
					String name = props.getProperty(ids + ".name");
					String cons = props.getProperty(ids + ".consumption");
					try {
						int id = Integer.parseInt(ids);
						long cn = Long.parseLong(cons);
						Task t = new Task(id, name, cn);
						t.setActionListener(this);
						tasks.add(t);
					} catch (NumberFormatException e) {
						JOptionPane.showMessageDialog(null, "Cannot load data from file (bad format).", "Error", JOptionPane.ERROR_MESSAGE);
					}                    
				}
			}

			Collections.sort(tasks);

			fireTableDataChanged();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Saves application's data to the file.
	 */
	public synchronized void saveToFile() {
		File dir = getDir();
		if (!dir.exists()) {
			if (!dir.mkdir()) {
				JOptionPane.showMessageDialog(null, "Cannot save data to file (cannot create data directory).", "Error", JOptionPane.ERROR_MESSAGE);
				return;
			}
		}

		Properties props = new Properties();
		props.setProperty("window.location.x", Integer.toString(taskFrame.getX()));
		props.setProperty("window.location.y", Integer.toString(taskFrame.getY()));
		props.setProperty("window.size.w", Integer.toString(taskFrame.getWidth()));
		props.setProperty("window.size.h", Integer.toString(taskFrame.getHeight()));
		for (int i=0; i<tasks.size(); i++) {
			Task t = tasks.get(i);
			String id = Integer.toString(t.getId());
			props.setProperty(id + ".name", t.getName());
			props.setProperty(id + ".consumption", Long.toString(t.getConsumption()));
		}

		try {
			FileOutputStream os = new FileOutputStream(getPath());
			props.storeToXML(os, "TimeAllocator task data");
			os.close();
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, "Cannot save data to file (" + e.getLocalizedMessage() + ").", "Error", JOptionPane.ERROR_MESSAGE);
		}
	}

	/**
	 * Processes an action event.
	 * 
	 * If the event has been generated by the auto-save timer it saves
	 * the data. Otherwise (a button action occurred) it updates
	 * the appropriate table cell.
	 * @param e action event
	 */
	public void actionPerformed(ActionEvent e) {
		Object src = e.getSource();
		if (src == timer) {
			saveToFile();
		}
		else {
			/* int row = tasks.indexOf(src); */
			for (int row=0; row<tasks.size(); row++) {
				fireTableCellUpdated(row, 1);
				fireTableCellUpdated(row, 2);
			}

		}
	}

}

